from api.extension import db
from api.lib.exception import CommitException

import datetime
import six

###################### 基础公共模型类定义 ######################

# 定义一些公共的方法，如to_dict()；
# 因为jsonify()只能序列化基本的数据类型如字典、列表、字符串、数字等，模型实例不是JSON可序列化的
class FormatMixin(object):
     
     def to_dict(self):
        res = dict()
        obj = getattr(self, "__mapper__")
        for key in obj.c.keys():
            if key in {'password', '_password', 'secret', '_secret'}:
                continue
            if key.startswith('_'):
                key = key[1:]
            if not isinstance(getattr(self, key), (datetime.date, datetime.time, datetime.datetime)):
                res[key] = getattr(self, key)
            else: 
                res[key] = str(getattr(self, key))
        return res
     

# 公共主键字段模型，id，整型自增
class PKMinix(object):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)


# 软删除(逻辑删除)，deleted，bool类型
class SoftDeleteMixin(object):
    # delete_at = db.Column(db.DateTime)
    deleted = db.Column(db.Boolean, index=True, default=False)

# 审计时间，表中字段如下：
# 1、created_at：创建时间，格式dateTime，默认当前时间
# 2、updated_at：更新时间，格式dateTime，默认当前时间
class TimestampMixin(object):
    create_time = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
    update_time = db.Column(db.DateTime, onupdate=lambda: datetime.datetime.now())


# 审计字段
class AuditMixin(object):
    create_by = db.Column(db.String(64), comment="创建人")
    create_time = db.Column(db.DateTime, default=lambda: datetime.datetime.now())
    update_by = db.Column(db.String(64), comment="修改人")
    update_time = db.Column(db.DateTime, onupdate=lambda: datetime.datetime.now())


###################### 基础公共模型类的方法定义 ######################

# 公共方法定义
class CRUDMixin(FormatMixin):

    @classmethod
    def create(cls, flush=False, commit=True, **kwargs):
        return cls(**kwargs).save(flush=flush, commit=commit)

    def save(self, commit=True, flush=False):
        db.session.add(self)
        try:
            if flush:
                db.session.flush()
            elif commit:
                db.session.commit()
        except Exception as e:
            db.session.rollback()
            raise CommitException(str(e))

        return self

    def update(self, flush=False, commit=True, filter_none=True, **kwargs):
        kwargs.pop("id", None)
        for attr, value in six.iteritems(kwargs):
            if (value is not None and filter_none) or not filter_none:
                setattr(self, attr, value)

        return self.save(flush=flush, commit=commit)

    def delete(self, flush=False, commit=True):
        db.session.delete(self)
        try:
            if flush:
                return db.session.flush()
            elif commit:
                return db.session.commit()
        except Exception as e:
            db.session.rollback()
            raise CommitException(str(e))

    @classmethod
    def delete_by_id(cls, id):
        db.session.execute("delete from {0} where id = {1}".format(cls.__tablename__, id))
        db.session.commit()

    def soft_delete(self, flush=False, commit=True):
        setattr(self, "deleted", True)
        setattr(self, "deleted_at", datetime.datetime.now())
        self.save(flush=flush, commit=commit)

    @classmethod
    def get_by_id(cls, id):
        if any((isinstance(id, six.string_types) and id.isdigit(), isinstance(id, (six.integer_types, float))),):
            # 方法 getattr(cls, "query") 返回的是 flask_sqlalchemy.query.Query查询类对象，默认的查询对象
            # 等价于 my_user = session.query(App).get(5)
            obj = getattr(cls, "query").get(int(id))
            if obj and not getattr(obj, 'deleted', False):
                return obj

    @classmethod
    def get_by(cls, 
               first=False,
               to_dict=True,
               fl=None,
               exclude=None,
               deleted=False,
               use_master=False,
               only_query=False,
               **kwargs):
        db_session = db.session if not use_master else db.session().using_bind("master")
        fl = fl.strip().split(",") if fl and isinstance(fl, six.string_types) else (fl or [])
        exclude = exclude.strip().split(",") if exclude and isinstance(exclude, six.string_types) else (exclude or [])

        keys = cls.get_columns()
        fl = [k for k in fl if k in keys]
        fl = [k for k in keys if k not in exclude and not k.isupper()] if exclude else fl
        fl = list(filter(lambda x: "." not in x, fl))

        if hasattr(cls, "deleted") and deleted is not None:
            kwargs["deleted"] = deleted

        kwargs_for_func = {i[7:]: kwargs[i] for i in kwargs if i.startswith('__func_')}
        kwargs = {i: kwargs[i] for i in kwargs if not i.startswith('__func_')}

        if fl:
            query = db_session.query(*[getattr(cls, k) for k in fl])
        else:
            query = db_session.query(cls)

        query = query.filter_by(**kwargs)
        for i in kwargs_for_func:
            func, key = i.split('__key_')
            query = query.filter(getattr(getattr(cls, key), func)(kwargs_for_func[i]))

        if only_query:
            return query

        if fl:
            result = [{k: getattr(i, k) for k in fl} if to_dict else i for i in query]
        else:
            result = [i.to_dict() if to_dict else i for i in query]

        return result[0] if first and result else (None if first else result)

    @classmethod
    def get_by_like(cls, to_dict=True, deleted=False, **kwargs):
        query = db.session.query(cls)
        if hasattr(cls, "deleted") and deleted is not None:
            query = query.filter(cls.deleted.is_(deleted))

        for k, v in kwargs.items():
            query = query.filter(getattr(cls, k).ilike('%{0}%'.format(v)))
        return [i.to_dict() if to_dict else i for i in query]

###################### 基础公共模型父类定义 ######################


# 基础模型对象基类
class Model(PKMinix, SoftDeleteMixin, CRUDMixin, db.Model):
    __abstract__ = True


# 审计基础模型对象基类
class AuditModel(PKMinix, SoftDeleteMixin, AuditMixin, CRUDMixin, db.Model):
    __abstract__ = True
